iT邦幫忙

2022 iThome 鐵人賽

DAY 12
0

前言:

前幾天都是畫面很簡單的範例,從今天起就進入App UI設計的部分
大概會有五篇範例,都照著練(打)完,會有基本的UI 設計能力
寫寫一些APP的介面不算難事 :)

程式畫面預覽:

圖片1

範例影片:

影片

程式碼:

app.js

import * as React from 'react';
import { View, Text } from 'react-native';
import { StatusBar } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import COLORS from "./src/consts/Colors";
import HomeScreen from "./src/view/screens/HomeScreen"
import DetailsScreen from "./src/view/screens/DetailsScreen"

const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <StatusBar barStyle="dark-content" backgroundColor={COLORS.green} />
      <Stack.Navigator screenOptions={{ header: () => null }}>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App

src/view/screens/DetailsScreen.js

import { StyleSheet, Text, View, Image } from 'react-native';
import React from 'react';
import { SafeAreaView } from 'react-native-safe-area-context';
import Icon from 'react-native-vector-icons/MaterialIcons';
import COLORS from '../../consts/Colors';

const DetailsScreen = ({ navigation, route }) => {
    const plant = route.params
    // console.log(plant)
    return (
        <SafeAreaView style={{ flex: 1, backgroundColor: COLORS.white }}>
            <View style={styles.Header}>
                {/* 回上一頁的箭頭 圖示 */}
                <Icon name="arrow-back" size={28} onPress={() => navigation.goBack()} />
                <Icon name="shopping-cart" size={28} />
            </View>
            {/* 商品圖 */}
            <View style={styles.ImageContainer}>
                <Image
                    source={plant.img}
                    style={{ flex: 1, resizeMode: 'contain' }}
                />
            </View>
            {/* 商品簡介 */}
            <View style={styles.DetailsContainer}>
                <View style={{
                    marginLeft: 20,
                    flexDirection: "row",
                    alignItems: "flex-end"
                }}>
                    <View style={styles.Line} />
                    <Text style={{ fontSize: 18, fontWeight: "bold" }}>Best choice</Text>
                </View>
                {/* 商品名稱 */}
                <View style={{
                    marginLeft: 20,
                    marginTop: 20,
                    flexDirection: "row",
                    justifyContent: "space-between",
                    alignItems: "center"
                }}>
                    <Text style={{ fontSize: 22, fontWeight: "bold" }}>{plant.name}</Text>
                    {/* 價錢的標籤區塊 */}
                    <View style={styles.PriceTag}>
                        {/* 價錢 text */}
                        <Text style={{
                            marginLeft: 15,
                            fontSize: 16,
                            fontWeight: "bold",
                            color: COLORS.white
                        }}>${plant.price}
                        </Text>
                    </View>
                </View>
                {/* 商品簡介 區塊 */}
                <View style={{
                    paddingHorizontal: 20,
                    marginTop: 10
                }}>
                    <Text style={{ fontSize: 20, fontWeight: "bold" }}>About</Text>
                    {/* 商品介紹文字 */}
                    <Text style={{ color: COLORS.grey, fontSize: 16, lineHeight: 22, marginTop: 10 }}>{plant.about}</Text>
                    {/* 商品訂購 區塊 */}
                    <View style={{ marginTop: 20, flexDirection: "row", justifyContent: "space-between" }}>
                        <View style={{ flexDirection: "row", alignItems: "center" }}>
                            <View style={styles.BorderBtn}>
                                <Text style={styles.BorderBtnText}>-</Text>
                            </View>
                            <Text style={{ marginHorizontal: 10, fontSize: 20, fontWeight: "bold" }}>1</Text>
                            <View style={styles.BorderBtn}>
                                <Text style={styles.BorderBtnText}>+</Text>
                            </View>
                        </View>
                        <View style={styles.BuyBtn}>
                            <Text style={{ color: COLORS.white, fontSize: 18, fontWeight: "bold" }}>Buy</Text>
                        </View>
                    </View>
                </View>
            </View>
        </SafeAreaView >
    );
};

export default DetailsScreen;

const styles = StyleSheet.create({
    Header: {
        paddingHorizontal: 20,
        marginTop: 20,
        flexDirection: "row",
        justifyContent: "space-between",
    },
    ImageContainer: {
        flex: 0.3,
        marginTop: 20,
        justifyContent: "center",
        alignItems: "center",

    },
    DetailsContainer: {
        flex: 0.7,
        backgroundColor: COLORS.light,
        marginHorizontal: 7,
        marginBottom: 7,
        borderRadius: 20,
        marginTop: 30,
        paddingTop: 30,
    },
    Line: {
        width: 25,
        height: 2,
        backgroundColor: COLORS.dark,
        marginBottom: 5,
        marginRight: 3,
    },
    PriceTag: {
        backgroundColor: COLORS.green,
        width: 80,
        height: 40,
        borderTopLeftRadius: 20,
        borderBottomLeftRadius: 20,
        justifyContent: "center",

    },
    BorderBtn: {
        borderColor: COLORS.grey,
        borderWidth: 1,
        borderRadius: 5,
        width: 60,
        height: 40,
        justifyContent: "center",
        alignItems: "center"
    },
    BorderBtnText: {
        fontSize: 28,
        fontWeight: "bold",
    },
    BuyBtn: {
        width: 100,
        height: 40,
        backgroundColor: COLORS.green,
        justifyContent: "center",
        alignItems: "center",
        borderRadius: 20,

    },
});

src/view/screens/HomeScreen.js

import { StyleSheet, Text, View, TouchableOpacity, FlatList, Dimensions, Image } from 'react-native';
import React, { useState } from 'react';
import { SafeAreaView } from 'react-native-safe-area-context';
import COLORS from '../../consts/Colors';
import Icon from 'react-native-vector-icons/MaterialIcons';
import { TextInput } from 'react-native-gesture-handler';

// 商品資料
import plants from '../../consts/Plants';

//商品框 寬度設定
const width = Dimensions.get("screen").width / 2 - 30

const HomeScreen = ({ navigation }) => {
    const categories = ['POPULAR', 'ORGANIC', 'INDOORS', 'SYNTHETIC'];
    const [categoryIndex, setCategoryIndex] = useState(0)

    const CategoryList = () => {
        return (
            <View style={styles.CategoryContainer}>
                {categories.map((item, index) => (
                    <TouchableOpacity key={index}
                        activeOpacity={0.8}
                        onPress={() => setCategoryIndex(index)} >
                        <Text style={[styles.CategoryText, categoryIndex == index && styles.CategoryIndexSelected]}>{item}</Text>
                    </TouchableOpacity>
                ))}
            </View>
        )
    }

    // 商品卡片 plant 要用{} 
    const Card = ({ plant }) => {
        return (
            <TouchableOpacity onPress={() => navigation.navigate("Details", plant)}>
                <View style={styles.Card} >
                    <View style={{ alignItems: "flex-end" }}>
                        <View style={{
                            width: 30,
                            height: 30,
                            borderRadius: 15,
                            alignItems: "center",
                            justifyContent: "center",
                            backgroundColor: plant.like
                                ? "rgba(245, 42, 42,0.2)"
                                : "rgba(0,0,0,0.2) ",
                        }}>
                            <Icon name="favorite" size={18} color={plant.like ? COLORS.red : COLORS.dark} />
                        </View>
                    </View>
                    {/* 商品圖 */}
                    <View style={{ height: 100, alignItems: "center" }}>
                        <Image
                            source={plant.img}
                            style={{ flex: 1, resizeMode: 'contain' }}
                        />
                    </View>
                    {/* 商品名稱 */}
                    <Text style={{ fontSize: 16, fontWeight: "bold", marginTop: 10 }}>{plant.name}</Text>
                    {/* 商品價格 跟 +號 */}
                    <View style={{ flexDirection: "row", justifyContent: "space-between", marginTop: 5 }}>
                        <Text style={{ fontSize: 18, fontWeight: "bold" }} > ${plant.price}</Text>
                        <View style={{
                            width: 25,
                            height: 25,
                            backgroundColor: COLORS.green,
                            borderRadius: 5,
                            justifyContent: "center",
                            alignItems: "center",
                        }}>
                            {/* 影片直接用 text + 看起來沒有置中對齊 改用icon 看起來比較整齊 */}
                            {/* icon 展示 https://oblador.github.io/react-native-vector-icons/ */}
                            <Icon name="add" style={{ fontSize: 22, fontWeight: "bold", color: COLORS.white }} />
                        </View>
                    </View>
                </View >
            </TouchableOpacity>

        )
    }

    return (
        <SafeAreaView style={{
            flex: 1,
            paddingHorizontal: 20,
            backgroundColor: COLORS.white
        }}>
            <View style={styles.Header}>
                <View>
                    <Text style={{ fontSize: 25, fontWeight: "bold" }}>Welcome to</Text>
                    <Text style={{ fontSize: 38, fontWeight: "bold", color: COLORS.green }}>Plant Shop</Text>
                </View>
                <Icon name="shopping-cart"
                    size={28}
                />
            </View>
            <View style={{ marginTop: 30, flexDirection: "row" }}>
                <View style={styles.SearchContainer}>
                    <Icon
                        name="search"
                        size={25}
                        style={{ marginLeft: 20 }}
                    />
                    <TextInput
                        placeholder='請輸入關鍵字'
                        style={styles.TextInput}
                    />
                </View>
                <View style={styles.SortBtn}>
                    <Icon name="sort" size={30} color={COLORS.white} />
                </View>
            </View>
            <CategoryList />
            {/* 商品展示區 */}
            <FlatList
                columnWrapperStyle={{ justifyContent: "space-between" }}
                showsVerticalScrollIndicator={false}
                contentContainerStyle={{
                    marginTop: 10,
                    paddingBottom: 50,
                }}
                numColumns={2}
                data={plants}
                renderItem={({ item }) => {
                    return <Card plant={item} />;
                }}
            />
        </SafeAreaView>
    )
};

export default HomeScreen;

const styles = StyleSheet.create({
    Header: {
        marginTop: 20,
        flexDirection: "row",
        justifyContent: "space-between",

    },
    SearchContainer: {
        height: 50,
        backgroundColor: COLORS.light,
        borderRadius: 10,
        // 讓兩個並排的關鍵
        flex: 1,
        flexDirection: "row",
        alignItems: "center",
    },
    TextInput: {
        marginLeft: 10,
        fontSize: 18,
        fontWeight: "bold",
        color: COLORS.dark,
        flex: 1,
    },
    SortBtn: {
        marginLeft: 10,
        width: 50,
        height: 50,
        backgroundColor: COLORS.green,
        justifyContent: "center",
        alignItems: "center",
        borderRadius: 10,
    },
    CategoryContainer: {
        flexDirection: "row",
        marginTop: 30,
        marginBottom: 20,
        justifyContent: "space-between",
    },
    CategoryText: {
        fontSize: 16,
        fontWeight: "bold",
        color: COLORS.grey,
    },
    CategoryIndexSelected: {
        color: COLORS.green,
        paddingBottom: 5,
        borderBottomWidth: 2,
        borderColor: COLORS.green,
    },
    Card: {
        width,
        height: 225,
        backgroundColor: COLORS.light,
        marginHorizontal: 2,
        borderRadius: 10,
        marginBottom: 20,
        padding: 15,

    },

});

範例 Source Code:

git clone https://smilehsu@bitbucket.org/smilehsu/yt_example_plantui0128.git

參考資料:

  1. 影片內容的程式碼: hakymz/PlantAppReactNative
  2. React Navigation 官網
  3. 卡卡30天學 React Native 系列 第13篇

上一篇
DAY11 - 看YT學React Native - 使用條碼模組(Barcode)
下一篇
DAY13 - 看YT學React Native - UI 範例2
系列文
總是學不來的中年大叔,孤獨的自學之旅第二年30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言